package buntdbutil

import (
	"encoding/json"
	"errors"
	"os"
	"path/filepath"
	"reflect"
	"sync"

	Log "github.com/cihub/seelog"
	"github.com/tidwall/buntdb"
)

var (
	dbMap     = map[string]*buntdb.DB{}
	loadLocks sync.Mutex
	fileRoot  string
)

func GetDb(dbName string) (*buntdb.DB, error) {
	loadLocks.Lock()
	defer loadLocks.Unlock()

	if db, ok := dbMap[dbName]; ok {
		dbFile := filepath.Join(fileRoot, dbName)
		_, err := os.Stat(dbFile)
		if !os.IsNotExist(err) { //文件存在则说明没有被清理,可以继续用
			return db, nil
		}

		delete(dbMap, dbName) //文件已经不存在,删除
	}

	if fileRoot == "" {
		root := ""
		exePath, err := os.Executable()
		if err != nil {
			root = "."
		}

		root, _ = filepath.EvalSymlinks(filepath.Dir(exePath))
		fileRoot = root
	}

	dbFile := filepath.Join(fileRoot, dbName)
	_, err := os.Stat(dbFile)
	if os.IsNotExist(err) { //文件不存在则创建
		if err := os.MkdirAll(filepath.Dir(dbFile), os.ModePerm); err != nil {
			Log.Error("创建目录失败:", err)
			return nil, errors.New("创建目录失败")
		}
	}

	db, err := buntdb.Open(dbFile)
	if err != nil {
		Log.Error("打开库文件异常:", err)
		return nil, errors.New("打开库文件异常")
	}

	dbMap[dbName] = db // 存储最终结果

	return db, nil
}

/**
 * 保存数据
 * @param dbName 数据库名称
 * @param key 键
 * @param value 值
 * @param indexKeys 映射
 * @return error 错误信息
 */
func Save(dbName, key string, value interface{}, indexKeys ...string) error {
	db, err := GetDb(dbName)
	if err != nil {
		return err
	}

	data, _ := json.Marshal(value)

	err = db.Update(func(tx *buntdb.Tx) error {
		_, _, err := tx.Set(key, string(data), nil) // 存储用户数据
		if err != nil {
			return err
		}

		for _, indexKey := range indexKeys { // 存储 映射
			_, _, err := tx.Set(indexKey, key, nil)
			if err != nil {
				return err
			}
		}

		return nil
	})

	return err
}

/**
 * 事务保存数据
 * @param dbName 数据库名称
 * @param data
 * @return error 错误信息
 */
func Saves(dbName string, data map[string]interface{}) error {
	db, err := GetDb(dbName)
	if err != nil {
		return err
	}

	err = db.Update(func(tx *buntdb.Tx) error {
		for key, value := range data {
			val, _ := json.Marshal(value)
			_, _, err := tx.Set(key, string(val), nil)
			if err != nil {
				return err
			}
		}

		return nil
	})

	return err
}

/**
 * 读取数据
 * @param dbName 数据库名称
 * @param key 键
 * @return string,error 值,错误信息
 */
func Read(dbName, key string, v any) error {
	db, err := GetDb(dbName)
	if err != nil {
		return err
	}

	err = db.View(func(tx *buntdb.Tx) error {
		val, err := tx.Get(key)
		if err != nil {
			return err
		}
		return json.Unmarshal([]byte(val), v)
	})

	return err
}

/**
 * 删除数据
 * @param dbName 数据库名称
 * @param key 键
 * @return error 错误信息
 */
func Del(dbName, key string) error {
	db, err := GetDb(dbName)
	if err != nil {
		return err
	}

	err = db.Update(func(tx *buntdb.Tx) error {
		_, err := tx.Delete(key) // 删除映射关系
		return err
	})

	return err
}

/**
 * 删除数据
 * @param dbName 数据库名称
 * @param key 键
 * @return error 错误信息
 */
func Dels(dbName string, keys []string) error {
	db, err := GetDb(dbName)
	if err != nil {
		return err
	}

	err = db.Update(func(tx *buntdb.Tx) error {
		for _, key := range keys {
			_, err := tx.Delete(key) // 删除映射关系
			return err
		}

		return nil
	})

	return err
}

/**
 * 检查键是否存在
 * @param dbName 数据库名称
 * @param key 键
 * @return bool,error 键是否存在,错误信息
 */
func HasKey(dbName, key string) (bool, error) {
	db, err := GetDb(dbName)
	if err != nil {
		return false, err
	}

	result := false
	err = db.Update(func(tx *buntdb.Tx) error {
		_, err := tx.Get(key)
		if err == nil {
			result = true
		}

		return nil
	})

	return result, err
}

/**
 * 列出数据库所有键值对
 * @param dbName 数据库名称
 * @return map error 键值对,错误信息
 */
func FindAll(dbName string, refer interface{}) (map[string]interface{}, error) {
	db, err := GetDb(dbName)
	if err != nil {
		return make(map[string]interface{}, 0), err
	}

	result := map[string]interface{}{}

	err = db.View(func(tx *buntdb.Tx) error {
		return tx.Ascend("", func(key, value string) bool {
			v := reflect.New(reflect.TypeOf(refer)).Interface()
			json.Unmarshal([]byte(value), &v)
			result[key] = v
			return true
		})
	})

	return result, err
}
